UIKitとSwiftUIでテキストフィールド付きアラートを表示する
テキストフィールド付きのアラートを表示させる機会があったので、UIKitとSwiftUIでの方法を調べてみました。
環境
- Xcode 13.3
- iOS 15.4
テキストフィールド付きアラートを表示
UIKit
デモ
このようなアラートを作成しました。
コード
import UIKit class ViewController: UIViewController { @IBOutlet private weak var inputtedLabel: UILabel! @IBAction private func showAlertWithTextField() { var textFieldOnAlert = UITextField() let alert = UIAlertController(title: "テキスト入力", message: nil, preferredStyle: .alert) alert.addTextField { textField in textFieldOnAlert = textField textFieldOnAlert.returnKeyType = .done } let doneAction = UIAlertAction(title: "決定", style: .default) { _ in self.inputtedLabel.text = textFieldOnAlert.text } let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel) alert.addAction(doneAction) alert.addAction(cancelAction) present(alert, animated: true) } }
Viewの真ん中にあるアラート表示ボタンを押すと、テキストフィールド付きのアラートが表示されます。アラート上のTextField
に任意のテキストを入力後に決定ボタン、またはキーボードのdoneキーを押すと、TextField
のテキストがラベルに反映されるようになっています。
addTextField
UIAlertController
にUITextField
を追加するには、addTextField
を使用します。
alert.addTextField { textField in textFieldOnAlert = textField textFieldOnAlert.returnKeyType = .done }
texFiled.delegate = self
のようにDelegateに準拠させることもできますが、今回は決定ボタンを押した時のテキストを取得したかっただけなのでDelegateは使用しておりません。
doneAction
今回は決定ボタンを押した時にアラート上のtextFieldOnAlert.text
をinputtedLabel.text
に代入しています。
let doneAction = UIAlertAction(title: "決定", style: .default) { _ in self.inputtedLabel.text = textFieldOnAlert.text }
キーボードの決定キーを押した際もこちらの処理が呼ばれます。
SwiftUI
iOS 15までには、SwiftUIの標準でテキストフィールド付きアラートが存在しません。今回はUIViewControllerRepresentable
を活用して実現することにしました。
デモ
このようなアラートを作成しました。
コード
AlertControllerWithTextFieldContainer
まずはUIAlertController
をSwiftUIで使えるようにする為に、UIViewControllerRepresentable
の構造体を作ります。
struct AlertControllerWithTextFieldContainer: UIViewControllerRepresentable { @Binding var textFieldText: String @Binding var isPresented: Bool let title: String? let message: String? let placeholderText: String func makeUIViewController(context: Context) -> UIViewController { return UIViewController() } // SwiftUIから新しい情報を受け、viewControllerが更新されるタイミングで呼ばれる func updateUIViewController(_ uiViewController: UIViewController, context: Context) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) // TextFieldの追加 alert.addTextField { textField in textField.placeholder = placeholderText textField.returnKeyType = .done } // 決定ボタンアクション let doneAction = UIAlertAction(title: "決定", style: .default) { _ in if let textField = alert.textFields?.first, let text = textField.text { textFieldText = text } } // キャンセルボタンアクション let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel) alert.addAction(cancelAction) alert.addAction(doneAction) DispatchQueue.main.async { uiViewController.present(alert, animated: true) { isPresented = false } } } }
プロパティの説明ですが、
- TextfieldText
- バインディングしてテキストを更新する為の変数
- isPresented
- アラートの表示非表示フラグ
- title
- アラートのタイトル
- message
- アラートのメッセージ
- placeholderText
- アラートのテキストフィールドのplaceholderテキスト
SwiftUIから新しい情報を受け、viewControllerが更新されるタイミングで呼ばれるメソッドupdateUIViewController
でアラートの表示処理を行なっています。
次はこのAlertControllerWithTextFieldContainer
をSwiftUIっぽく使えるようにする為に、カスタムModifierを作成します。
カスタムModifierの定義
まずはViewModifier
としてのAlertWithTextField
を定義します。
// カスタムModifierの定義 struct AlertWithTextField: ViewModifier { @Binding var textFieldText: String @Binding var isPresented: Bool let title: String? let message: String? let placeholderText: String func body(content: Content) -> some View { ZStack { content if isPresented { AlertControllerWithTextFieldContainer(textFieldText: $textFieldText, isPresented: $isPresented, title: title, message: message, placeholderText: placeholderText) } } } }
カスタムModifierをSwiftUIっぽく使用できるようにする
extension View { // カスタムModifierのメソッド名を alertWithTextField に置き換え func alertWithTextField(_ text: Binding<String>, isPresented: Binding<Bool>, title: String?, message: String?, placeholderText: String) -> some View { self.modifier(AlertWithTextField(textFieldText: text, isPresented: isPresented, title: title, message: message, placeholderText: placeholderText)) } }
これでView
のModifierとしてalertWithTextField
が使用できるようになりました。
ContentView
ContentView
で使用した例になります。
import SwiftUI struct ContentView: View { @State private var text = "入力されたテキスト" @State private var shouldPresentAlert = false var body: some View { VStack(spacing: 50) { Text(text) .bold() Button { shouldPresentAlert.toggle() } label: { Text("アラート表示") } } .alertWithTextField($text, isPresented: $shouldPresentAlert, title: "テキスト入力", message: nil, placeholderText: "") } }
アラート表示というボタンを押下した時に、アラートの表示フラグが切り替わり、アラートが表示されるようになっております。
おわりに
SwiftUIでテキストフィールド付きアラートを表示しようと試みましたが、かなり記述量が多くなってしまいました。早くSwiftUIのAlertもテキストフィールドをスマートに追加できるようになるといいですね。
How to add a TextField to Alert in SwiftUIの記事を見ると、iOS 16からはスマートに実装できるようになりそうです。